﻿using dz.loger.core;
using dz.util.core;
using Dz.WorkerService.jkyschema;
using Dz.WorkerService.QueryParm;
using Dz.WorkerService.Service;
using System.Collections.Concurrent;
using System.Data;
using System.Threading;

namespace Dz.WorkerService
{
    public class TaskSo_Test : BaseTaskItem
    {
        private static int cnt = 0;
        
        private string fields = @"tradeNo,chargeCurrency,goodsDetail.goodsNo,goodsDetail.sellCount,goodsDetail.unit,goodsDetail.platCode,goodsDetail.platGoodsId,
                            customerCode,warehouseCode,warehouseName,tradeId,shopcode,scrollId,goodsDetail.isFit,flagNames,goodsDetail.sellTotal,goodsDetail.taxFee,
                            tradeOrderAssemblyGoodsDtoList.sellTotal,tradeOrderAssemblyGoodsDtoList.taxFee,tradeOrderAssemblyGoodsDtoList.goodsNo,consignTime,
                            tradeOrderAssemblyGoodsDtoList.taxRate,tradeOrderAssemblyGoodsDtoList.sellCount,tradeOrderAssemblyGoodsDtoList.sellTotal,flagNames,
                            goodsDetail.subTradeId,tradeOrderAssemblyGoodsDtoList.subTradeId,isBillCheck,billDate,isDelete,tradeStatus,payment";

        private DataTable data;
        
        public TaskSo_Test(string name, TimeSpan interval) : base(name, interval)
        {
            docType = "so";
            method = "oms.trade.fullinfoget";
            table = "dz_jky_SO";
            data = GetTable();            

        }

        protected override async Task SyncToU9()
        {
            OfflineSipService offlineSip = new OfflineSipService(processId);
            TransferinService transferin = new TransferinService(processId);

            await offlineSip.ImportData();
            await transferin.ImportData();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sdate"></param>
        /// <param name="edate"></param>
        /// <param name="type">1:按发货时间 2：按对账时间</param>
        /// <returns></returns>
        private BaseParm BuildParm(DateTime sdate, DateTime edate, int type)
        {
            if (type == 1) return BuildOffLineParm(sdate, edate);

            return BuildOnlineParm(sdate, edate);
        }

        private BaseParm BuildOffLineParm(DateTime sdate, DateTime edate)
        {
            TransferInParm parm = new TransferInParm();

            parm.startConsignTime = sdate.ToString("yyyy-MM-dd HH:mm:ss");
            parm.endConsignTime = edate.ToString("yyyy-MM-dd HH:mm:ss");

            parm.fields = fields;

            return parm;
        }

        private BaseParm BuildOnlineParm(DateTime sdate, DateTime edate)
        {
            TransferInParm parm = new TransferInParm();

            parm.startBillDate = sdate.ToString("yyyy-MM-dd HH:mm:ss"); ;
            parm.endBillDate = edate.ToString("yyyy-MM-dd HH:mm:ss");

            parm.fields = fields;

            return parm;
        }
        

        /// <summary>
        /// 获得数据表
        /// </summary>
        /// <returns></returns>
        private DataTable GetTable()
        {
            DataTable table = new DataTable();


            table.Columns.Add("shopcode", typeof(string));
            table.Columns.Add("deptcode", typeof(string));
            table.Columns.Add("org", typeof(string));
            table.Columns.Add("wh", typeof(string));
            table.Columns.Add("tradeno", typeof(string));
            table.Columns.Add("tradeid", typeof(string));
            table.Columns.Add("parent", typeof(string));
            table.Columns.Add("goodsno", typeof(string));
            table.Columns.Add("sellcount", typeof(decimal));
            table.Columns.Add("sellamt", typeof(decimal));
            table.Columns.Add("orgamt", typeof(decimal));
            table.Columns.Add("platcode", typeof(string));
            table.Columns.Add("shipdate", typeof(DateTime));
            table.Columns.Add("isfit", typeof(int));
            table.Columns.Add("flag", typeof(int));
            table.Columns.Add("cust", typeof(string));
            table.Columns.Add("price", typeof(decimal));
            table.Columns.Add("payment", typeof(decimal));

            return table;
        }
        private record Item(string Id, string Code, decimal Amt);

  

        private void AddData(ConcurrentBag<DataRow> rows, TradeItem item, int xtype)
        {
            try
            {
                string a = item.tradeNo;

                if (a == "JY2025083110498")
                {
                    int r = 0;
                }
                List<Item> items = new List<Item>();

                int flag = (xtype == 1) ? 1 : 2;

                decimal payment = item.payment;
                decimal total = 0;
                string firstParent = string.Empty;

                string flagNames = item.flagNames ?? string.Empty;

                if (item.isDelete == 1 || flagNames.Contains("无效订单") || string.IsNullOrEmpty(item.warehouseCode)
                    || item.tradeStatus == "5010" || flagNames.Contains("补录单")) return;
                //if (item.flagNames.Contains("补录单")) return;

                //如果是有对账日期，但对账状态不是已对账的，不处理
                //处理生成线上出货数据

                if (xtype == 2 && (item.isBillCheck != 1 || flagNames.Contains("线下订单"))) return;



                if (flagNames.Contains("线下订单"))
                {
                    flag = 3;
                }
                if (item.goodsDetail == null || item.goodsDetail.Count == 0) return;

                List<DataRow> localrows = new List<DataRow>();

                foreach (var row in item.goodsDetail)
                {
                    //组合件需要从组装明细获取
                    if (row.isFit == 1)
                    {
                        items.Add(new Item(row.subTradeId, row.goodsNo, row.sellTotal));
                        if (firstParent.Length == 0) firstParent = row.goodsNo;
                        continue;
                    }

                    DataRow dr = data.NewRow();
                    //DataRow dr = rows.ElementAt(0).Table.NewRow();

                    dr["payment"] = item.payment;
                    dr["shopcode"] = item.shopCode;
                    dr["deptcode"] = item.shopCode.Split("-")[0];
                    dr["org"] = item.shopCode.Split("-")[1];
                    dr["wh"] = item.warehouseCode;
                    dr["tradeno"] = item.tradeNo;
                    dr["tradeid"] = item.tradeId;
                    dr["goodsno"] = row.goodsNo;
                    dr["sellcount"] = row.sellCount;
                    dr["platcode"] = row.platCode;
                    dr["flag"] = flag;
                    dr["sellamt"] = row.sellTotal;
                    dr["orgamt"] = row.sellTotal;
                    total = total + row.sellTotal;
                    dr["isfit"] = 0;
                    dr["price"] = row.sellCount == 0 ? Math.Abs(row.sellTotal) : Math.Round(row.sellTotal / row.sellCount, 6);

                    dr["shipdate"] = (xtype == 1) ? item.consignTime.Date : item.billDate.Date;
                    dr["cust"] = item.customerCode;

                    localrows.Add(dr);
                }

                ProcessFit(ref localrows, item,  items, flag, ref total,xtype);

                #region 调整金额
                if (payment != total)
                {
                    decimal dif = payment - total;

                    if (firstParent.Length == 0)
                    {
                        decimal amt = Convert.ToDecimal(localrows[0]["sellamt"]) + dif;
                        localrows[0]["sellamt"] = amt;
                        localrows[0]["orgamt"] = amt;
                        decimal sellCount = Convert.ToDecimal(localrows[0]["sellcount"]);
                        localrows[0]["price"] = sellCount == 0 ? Math.Abs(amt) : Math.Round(amt / sellCount, 6);
                    }
                    else
                    {
                        foreach (DataRow dr in localrows)
                        {
                            if (dr["parent"].ToString() != firstParent) continue;

                            decimal amt = Convert.ToDecimal(dr["sellamt"]) + dif;
                            dr["sellamt"] = amt;
                            dr["orgamt"] = amt;
                        }
                    }
                }
                #endregion

                foreach (var row in localrows)
                {
                    rows.Add(row);
                }
            }
            catch (Exception ex)
            {
                LogUtil.Instance.Error(ex);
                LogUtil.Instance.Error(ex.StackTrace);
                throw;
            }
        }



        private BaseParm RebuildParm(BaseParm parm, string scrollId)
        {
            TransferInParm rtn = parm as TransferInParm;

            rtn.scrollId = scrollId;

            return rtn;
        }

        protected override BaseParm BuildParm(DateTime sdate, DateTime edate)
        {
            TransferInParm parm = new TransferInParm();

            sDate = sdate;
            eDate = edate;

            parm.startConsignTime = sdate.ToString("yyyy-MM-dd HH:mm:ss");
            parm.endConsignTime = edate.ToString("yyyy-MM-dd HH:mm:ss");
            parm.fields = @"tradeNo,chargeCurrency,goodsDetail.goodsNo,goodsDetail.sellCount,goodsDetail.unit,goodsDetail.platCode,goodsDetail.platGoodsId,
                                    customerCode,warehouseCode,warehouseName,tradeId,shopcode,scrollId,goodsDetail.isFit,flagNames,goodsDetail.sellTotal,goodsDetail.taxFee,
                                    tradeOrderAssemblyGoodsDtoList.sellTotal,tradeOrderAssemblyGoodsDtoList.taxFee,tradeOrderAssemblyGoodsDtoList.goodsNo,consignTime,
                                    tradeOrderAssemblyGoodsDtoList.taxRate,tradeOrderAssemblyGoodsDtoList.sellCount,tradeOrderAssemblyGoodsDtoList.sellTotal,flagNames,
                                    goodsDetail.subTradeId,tradeOrderAssemblyGoodsDtoList.subTradeId,isBillCheck,billDate,isDelete,tradeStatus,payment";

            return parm;
        }

        protected override async Task DownLoadData(BaseParm parm)
        {
            data.Clear();
            await base.DownLoadData(parm);

            string scrollId = Guid.NewGuid().ToString();
            processId = scrollId;

            try
            {
                var ranges = SplitTimeRange(sDate, eDate,TimeSpan.FromHours(1));
                await InsertSyncLog(processId, docType);

                var options = new ParallelOptions
                {
                    MaxDegreeOfParallelism = 8, // 从较小的并发数开始                    
                };

                // 线程安全的集合用于收集结果
                var allData = new ConcurrentBag<DataRow>();

                var qpsLimiter = new QpsLimiter(maxQueriesPerSecond: 3);

                var tasks = ranges.Select(async timeRange =>
                {
                    await FetchOneHourAsync(timeRange.Start, timeRange.End, 1, allData, qpsLimiter, new CancellationToken());                   
                });

                await Task.WhenAll(tasks);

                var tasks1 = ranges.Select(async timeRange =>
                {                    
                    await FetchOneHourAsync(timeRange.Start, timeRange.End, 2, allData, qpsLimiter, new CancellationToken());
                });
                await Task.WhenAll(tasks1);
                // await Parallel.ForEachAsync(ranges, options, async (timeRange, cancellationToken) =>
                //{
                //    await FetchOneHourAsync(timeRange.Start, timeRange.End, 1, allData, cancellationToken);

                //});

                // await Task.Delay(500);

                // await Parallel.ForEachAsync(ranges, options, async (timeRange, cancellationToken) =>
                // {
                //     await FetchOneHourAsync(timeRange.Start, timeRange.End, 2, allData, cancellationToken);
                // });


                // 将并行收集的数据合并到主DataTable
                var rowsArray = allData.AsParallel()
                       .Select(row =>
                       {
                           DataRow newRow = data.NewRow();
                           newRow.ItemArray = row.ItemArray.Clone() as object[];
                           return newRow;
                       })
                       .ToArray();

                data.BeginLoadData();
                try
                {
                    foreach (var newRow in rowsArray)
                    {
                        data.Rows.Add(newRow);
                    }
                }
                finally
                {
                    data.EndLoadData();
                }

                int cnt = data.Rows.Count;

                await sqlUtils.Save(data, "dz_jky_SO");

                await UpdateSyncLog(processId, 1);
                await UpdateDataState(1);
                await AdjustAmt();
                await SetLastDate();
            }
            catch (Exception ex)
            {
                await UpdateSyncLog(processId, -1, ex.Message);
                LogUtil.Instance.Error($"同步{docType}失败：{ex.StackTrace}");
                throw new Exception(ex.Message);
            }
        }

        private async Task FetchOneHourAsync(DateTime start, DateTime end, int type, 
                                                        ConcurrentBag<DataRow> allData, QpsLimiter qpsLimiter, CancellationToken cancellationToken)
        {
            
            string scrollId = "";
            int pageCount = 1;
            DateTime stime, etime;

            bool flag = true;

            string msg = type == 1 ? "按发货时间下载数据" : "按对账时间下载数据";
            BaseParm parm = BuildParm(start, end, type);
            while (flag)
            {
                //await qpsLimiter.WaitForNextSlotAsync(cancellationToken);
                
                
                stime = DateTime.Now;
                WebResult result = await JkyUtils.PostByQM(method, JsonUtil.Serialize(parm));
                etime = DateTime.Now;
                int duration1 = (int)(etime - stime).TotalMilliseconds;
                #region 判断http请求是否成功
                if (!result.Sucess)
                    if (!result.Sucess)
                    {
                        flag = false;
                        throw new Exception($"下载{docType}失败：{result.Data}");
                    }
                #endregion

                #region 判断请求结构是否成功
                QmResult result1 = JsonUtil.Deserialize<QmResult>(result.Data);
                if (result1.jackyunCode != "200")
                {
                    flag = false;
                    throw new Exception($"下载{docType}失败：{result1.jackyunMessage}");
                }
                #endregion

                #region 处理请求结果

                scrollId = result1.scrollId;
                parm = RebuildParm(parm, scrollId);

                if (string.IsNullOrEmpty(scrollId)) flag = false;

                //if (result1.trades == null || result1.trades.Count == 0) continue;
                // 检查是否有数据
                if (result1.trades == null || result1.trades.Count == 0)
                {
                    LogUtil.Instance.Debug($"第{pageCount}页无数据");
                    
                    continue;
                    
                }
                stime = DateTime.Now;
                List<DateTime> dates = new List<DateTime>();
                foreach (TradeItem item in result1.trades)
                {
                    try
                    {
                        dates.Add(type == 1 ? item.consignTime : item.billDate);
                        AddData(allData, item, type);
                    }
                    catch (Exception ex)
                    {
                        LogUtil.Instance.Error($"错误单号:{item.tradeNo}");
                        throw ex;
                    }
                    
                }
                etime = DateTime.Now;
                int duration2 = (int)(etime - stime).TotalMilliseconds;
                #endregion

                string sql = $@"insert into dz_jky_download_log(stime,etime,doctype,cnt,due1,due2,msg,totalcnt,sdate,edate,scrollId)
                             values('{start}','{end}','so',{pageCount},{duration1},{duration2},'{msg}',{result1.trades.Count},'{dates.Min()}','{dates.Max()}','{scrollId}')";

                await sqlUtils.ExecuteNonQuery(sql);

                if (type == 1)
                {
                    LogUtil.Instance.Debug($"按发货时间下载数据：{start} {end}  第{pageCount}次 当前页记录数 {result1.trades.Count} 开始时间：{dates.Min()} 结束时间 {dates.Max()} {scrollId}");
                }
                else
                {
                    LogUtil.Instance.Debug($"按对账时间下载数据：{start} {end}  第{pageCount}次 当前页记录数 {result1.trades.Count} 开始时间：{dates.Min()} 结束时间 {dates.Max()} {scrollId}");
                }

                if (string.IsNullOrEmpty(scrollId))
                {
                    LogUtil.Instance.Debug($"时间段 {start:yyyy-MM-dd HH:mm} 到 {end:yyyy-MM-dd HH:mm} 查询完成，共 {pageCount - 1} 页数据");
                    break;
                }

                
                
                pageCount++;

            }
        }
        private void ProcessFit(ref List<DataRow> rows, TradeItem item, List<Item> items, int flag, ref decimal total,int xtype)
        {
            if (item.tradeOrderAssemblyGoodsDtoList == null || item.tradeOrderAssemblyGoodsDtoList.Count == 0) return;

            string parent = string.Empty;
            ///处理组装件
            foreach (var row in item.tradeOrderAssemblyGoodsDtoList)
            {

                DataRow dr = data.NewRow();


                dr["payment"] = item.payment;
                dr["shopcode"] = item.shopCode;
                dr["deptcode"] = item.shopCode.Split("-")[0];
                dr["org"] = item.shopCode.Split("-")[1];
                dr["wh"] = item.warehouseCode;
                dr["tradeno"] = item.tradeNo;
                dr["tradeid"] = item.tradeId;
                dr["goodsno"] = row.goodsNo;
                dr["shipdate"] = (xtype == 1) ? item.consignTime.Date : item.billDate.Date;                
                dr["sellcount"] = row.sellCount;
                dr["isfit"] = 1;
                Item find = items.FirstOrDefault(x => x.Id == row.subTradeId);
                dr["parent"] = find.Code;
                dr["sellamt"] = find.Amt;
                dr["orgamt"] = find.Amt;
                dr["flag"] = flag;
                dr["price"] = 0;
                dr["cust"] = item.customerCode;

                if (parent != find.Code)
                {
                    parent = find.Code;
                    total += find.Amt;
                }
                rows.Add(dr);

            }
        }
        
        private async Task AdjustAmt()
        {
            string sql = $"exec usp_dz_jky_adjustsoamt";

            await sqlUtils.ExecuteNonQuery(sql);
        }

        private List<TimeRange> SplitTimeRange(DateTime startDate, DateTime endDate, TimeSpan interval)
        {
            var ranges = new List<TimeRange>();
            var currentStart = startDate;

            while (currentStart < endDate)
            {
                var currentEnd = currentStart.Add(interval).AddSeconds(-1);
                if (currentEnd > endDate)
                {
                    currentEnd = endDate;
                }

                ranges.Add(new TimeRange { Start = currentStart, End = currentEnd });
                currentStart = currentEnd.AddSeconds(1);
                if (currentStart == endDate) break;
            }

            return ranges;
        }
    }
}

public class TimeRange
{
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
}

public class QpsLimiter
{
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(2, 4);
    private readonly int _minIntervalMs;
    private DateTime _lastRequestTime = DateTime.MinValue;

    public QpsLimiter(int maxQueriesPerSecond)
    {
        _minIntervalMs = 1000 / maxQueriesPerSecond;
    }

    public async Task WaitForNextSlotAsync(CancellationToken cancellationToken = default)
    {
        await _semaphore.WaitAsync(cancellationToken);
        try
        {
            var now = DateTime.Now;
            var timeSinceLastRequest = now - _lastRequestTime;
            var delayNeeded = _minIntervalMs - timeSinceLastRequest.TotalMilliseconds;

            if (delayNeeded > 0)
            {
                await Task.Delay((int)delayNeeded, cancellationToken);
            }

            _lastRequestTime = DateTime.Now;
        }
        finally
        {
            _semaphore.Release();
        }
    }
}
